package gretry

import (
	"math/rand/v2"
	"time"
)

// DelayCalculator 延迟计算器.
/*
	1. DCExponential（指数退避）：
	   使用场景: 指数退避增长迅速, 适用于给予服务器时间来恢复错误。

	2. DCExponential（线性退避）：
	   使用场景：适用于对错误尽快重试, 迫切的想要服务器尽快恢复, 线性退避的重试频率更高。

	DCWithJitter（抖动）：
	   使用场景：适用于让间隔时间更加分散。
*/
type DelayCalculator func(tried uint) time.Duration

// DCFixed 固定延迟时间.
func DCFixed(d time.Duration) DelayCalculator {
	return func(tried uint) time.Duration {
		return d
	}
}

// DCExponential 指数退避. 延迟从 min_ 开始, 最大延迟不超过 max_.
// 示例: DCExponential(100*time.Millisecond, 2*time.Second),
// 第一次尝试等待 100ms, 第二次尝试等待 200ms, 第三次尝试等待 400ms, 第四次尝试等待 800ms..., >2s时则只固定返回2s.
func DCExponential(min_, max_ time.Duration) DelayCalculator {
	if min_ <= 0 {
		panic("min_ must be positive")
	}
	if max_ <= 0 {
		panic("max_ must be positive")
	}
	return func(tried uint) time.Duration {
		return min(min_<<tried, max_)
	}
}

// DCLinear 线性退避. 延迟从 min_ 开始, 每次延迟增加固定时间, 最大延迟不超过 max_.
// 示例: DCLinear(100*time.Millisecond, 2*time.Second, 100*time.Millisecond),
// 第一次尝试等待 100ms, 第二次尝试等待 200ms, 第三次尝试等待 300ms, 第四次尝试等待 400ms..., >2s时则只固定返回2s.
func DCLinear(min_, max_ time.Duration, inc time.Duration) DelayCalculator {
	if min_ <= 0 {
		panic("min_ must be positive")
	}
	if max_ <= 0 {
		panic("max_ must be positive")
	}
	return func(tried uint) time.Duration {
		return min(min_+(time.Duration(tried)*inc), max_)
	}
}

// DCWithJitter 抖动, 将d计算的结果添加一个随机时间.
// rMin: 设置随机的最小时间. rMax: 设置随机的最大时间.
func DCWithJitter(d DelayCalculator, rMin, rMax time.Duration) DelayCalculator {
	if rMin < 0 {
		panic("rMin must be positive")
	}
	if rMax < 0 {
		panic("rMax must be positive")
	}
	r := rMax - rMin
	if r < 0 {
		panic("rMax-rMin must gte 0")
	}
	return func(tried uint) time.Duration {
		if r <= 1 {
			return d(tried) + rMin
		}
		return d(tried) + rand.N(r) + rMin
	}
}
